using ScottPlot.AxisLimitManagers;
using ScottPlot.DataSources;
using ScottPlot.DataViews;

namespace ScottPlot.Plottables;

public class DataStreamer : IPlottable, IManagesAxisLimits, IHasLine, IHasLegendText, IHasMarker
{
    public bool IsVisible { get; set; } = true;
    public IAxes Axes { get; set; } = ScottPlot.Axes.Default;
    public IEnumerable<LegendItem> LegendItems => LegendItem.Single(this, LegendText, LineStyle);

    [Obsolete("use LegendText")]
    public string Label { get => LegendText; set => LegendText = value; }
    public string LegendText { get; set; } = string.Empty;

    public LineStyle LineStyle { get; set; } = new() { Width = 1 };
    public float LineWidth { get => LineStyle.Width; set => LineStyle.Width = value; }
    public LinePattern LinePattern { get => LineStyle.Pattern; set => LineStyle.Pattern = value; }
    public Color LineColor { get => LineStyle.Color; set => LineStyle.Color = value; }

    public MarkerStyle MarkerStyle { get; set; } = new(MarkerShape.FilledCircle, 0);
    public MarkerShape MarkerShape { get => MarkerStyle.Shape; set => MarkerStyle.Shape = value; }
    public float MarkerSize { get => MarkerStyle.Size; set => MarkerStyle.Size = value; }
    public Color MarkerFillColor { get => MarkerStyle.FillColor; set => MarkerStyle.FillColor = value; }
    public Color MarkerLineColor { get => MarkerStyle.LineColor; set => MarkerStyle.LineColor = value; }
    public Color MarkerColor { get => MarkerStyle.MarkerColor; set => MarkerStyle.MarkerColor = value; }
    public float MarkerLineWidth { get => MarkerStyle.LineWidth; set => MarkerStyle.LineWidth = value; }
    public bool FillY { get; set; } = false;
    public double FillYValue { get; set; } = 0;
    public Color FillYAboveColor { get; set; } = Colors.Blue.WithAlpha(.2);
    public Color FillYBelowColor { get; set; } = Colors.Blue.WithAlpha(.2);
    public Color FillYColor { get => FillYAboveColor; set { FillYAboveColor = value; FillYBelowColor = value; } }

    public Color Color
    {
        get => LineStyle.Color;
        set
        {
            LineColor = value;
            MarkerFillColor = value;
        }
    }

    public DataStreamerSource Data { get; set; }
    public int Count => Data.Length;
    public int CountTotal => Data.CountTotal;

    public double Period { get => Data.SamplePeriod; set => Data.SamplePeriod = value; }

    /// <summary>
    /// Returns true if data has been added since the last render
    /// </summary>
    public bool HasNewData => Data.CountTotal != Data.CountTotalOnLastRender;

    /// <summary>
    /// If enabled, axis limits will be adjusted automatically if new data runs off the screen.
    /// </summary>
    public bool ManageAxisLimits { get; set; } = true;

    [Obsolete("set Plot.Axes.ContinuouslyAutoscale", true)]
    public bool ContinuouslyAutoscale { get; set; } = false;

    /// <summary>
    /// Contains logic for automatically adjusting axis limits if new data runs off the screen.
    /// Only used if <see cref="ManageAxisLimits"/> is true.
    /// </summary>
    private IAxisLimitManager AxisManager { get; set; } = new FixedWidth();

    /// <summary>
    /// Used to obtain the current axis limits so <see cref="AxisManager"/> can adjust them if needed.
    /// </summary>
    private readonly Plot Plot;

    /// <summary>
    /// Logic for displaying the fixed-length Y values in <see cref="Data"/>
    /// </summary>
    public DataViews.IDataStreamerView Renderer { get; set; }

    public DataStreamer(Plot plot, double[] data)
    {
        Plot = plot;
        Data = new DataStreamerSource(data);
        Renderer = new DataViews.Wipe(this, true);
    }

    /// <summary>
    /// Shift in a new Y value
    /// </summary>
    public void Add(double value)
    {
        Data.Add(value);
    }

    public void Add(double[] ys)
    {
        if (ys is null)
            throw new ArgumentException($"{nameof(ys)} must not be null");

        for (int i = 0; i < ys.Length; i++)
        {
            Data.Add(ys[i]);
        }
    }

    /// <summary>
    /// Shift in a collection of new Y values
    /// </summary>
    public void AddRange(IEnumerable<double> values)
    {
        Data.AddRange(values);
    }

    /// <summary>
    /// Clear the buffer by setting all Y points to the given value
    /// </summary>
    public void Clear(double value = 0)
    {
        Data.Clear(value);
    }

    /// <summary>
    /// Display the data using a view where new data overlaps old data from left to right.
    /// </summary>
    public void ViewWipeRight(double blankFraction = 0)
    {
        Renderer = new DataViews.Wipe(this, true) { BlankFraction = blankFraction };
    }

    /// <summary>
    /// Display the data using a view where new data overlaps old data from right to left.
    /// </summary>
    public void ViewWipeLeft()
    {
        Renderer = new DataViews.Wipe(this, false);
    }

    /// <summary>
    /// Display the data using a view that continuously shifts data to the left, placing the newest data on the right.
    /// </summary>
    public void ViewScrollLeft()
    {
        Renderer = new DataViews.Scroll(this, true);
    }

    /// <summary>
    /// Display the data using a view that continuously shifts data to the right, placing the newest data on the left.
    /// </summary>
    public void ViewScrollRight()
    {
        Renderer = new DataViews.Scroll(this, false);
    }

    /*
    // TODO: slide axes
    /// <summary>
    /// Display the data using a view that continuously shifts data to the left, 
    /// placing the newest data on the right, and sliding the horizontal axis
    /// to track the latest data coming in.
    /// </summary>
    public void ViewSlideLeft()
    {
        Renderer = new DataViews.Scroll(this, true);
    }
    */

    /// <summary>
    /// Display the data using a custom rendering function
    /// </summary>
    public void ViewCustom(DataViews.IDataStreamerView view)
    {
        Renderer = view;
    }

    public AxisLimits GetAxisLimits()
    {
        return Data.GetAxisLimits();
    }

    public void UpdateAxisLimits(Plot plot)
    {
        if (Data.CountTotal == 0)
            return;

        AxisLimits limits = Plot.Axes.GetLimits(Axes);
        AxisLimits dataLimits = GetAxisLimits();
        AxisLimits newLimits = AxisManager.GetAxisLimits(limits, dataLimits);
        Plot.Axes.SetLimits(newLimits, Axes.XAxis, Axes.YAxis);
    }

    public virtual void Render(RenderPack rp)
    {
        if (Renderer is not IDataStreamerView viewWithSegs)
        {
            Renderer.Render(rp);
            return;
        }

        var segs = viewWithSegs.GetSegments(rp);
        if (segs.Count == 0)
            return;

        if (FillY)
        {
            float yBasePx = Axes.YAxis.GetPixel(FillYValue + Data.OffsetY, rp.DataRect);

            foreach (Pixel[] seg in segs)
            {
                using SKPath path = new();
                path.MoveTo(seg[0].X, seg[0].Y);
                for (int i = 1; i < seg.Length; i++)
                    path.LineTo(seg[i].X, seg[i].Y);

                PixelRect rect = new(seg);
                using SKPath fill = new(path);
                fill.LineTo(rect.Right, yBasePx);
                fill.LineTo(rect.Left, yBasePx);

                // above
                if (yBasePx > rect.Top)
                {
                    PixelRect clip = new(rp.DataRect.Left, rp.DataRect.Right, rp.DataRect.Top, yBasePx);
                    FillStyle fs = new() { IsVisible = true, Color = FillYAboveColor };
                    rp.CanvasState.Save();
                    rp.CanvasState.Clip(clip);
                    Drawing.FillPath(rp.Canvas, rp.Paint, fill, fs, clip);
                    rp.CanvasState.Restore();
                }

                // below
                if (yBasePx < rect.Bottom)
                {
                    PixelRect clip = new(rp.DataRect.Left, rp.DataRect.Right, yBasePx, rp.DataRect.Bottom);
                    FillStyle fs = new() { IsVisible = true, Color = FillYBelowColor };
                    rp.CanvasState.Save();
                    rp.CanvasState.Clip(clip);
                    Drawing.FillPath(rp.Canvas, rp.Paint, fill, fs, clip);
                    rp.CanvasState.Restore();
                }
            }
        }

        Renderer.Render(rp);

        Data.CountTotalOnLastRender = Data.CountTotal;
    }
}
